package js.dwim.client;

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.net.ssl.SSLSocketFactory;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;

public class XmppComms implements IMessageReceiver
{
    private LinkedList<IMessageReceiver> msgReceivers;
	private XMPPConnection conn = null;
	private Chat chat 			= null;
	private String serverName;
	
	private String lastRequest;
	private String lastReply;
	
	private final Lock lock            = new ReentrantLock();
	private final Condition replyReady = lock.newCondition();
	
	public XmppComms(String serverName, String host, int port, String service) 
	        throws CommunicationException
	{
	    this.msgReceivers = new LinkedList<IMessageReceiver>();
	    this.serverName   = serverName;
	    
	    this.msgReceivers.add( this );
	    
		XMPPConnection.DEBUG_ENABLED 	    = false;
		ConnectionConfiguration connConfig 	= new ConnectionConfiguration(host, port, service);
		
		connConfig.setSecurityMode( ConnectionConfiguration.SecurityMode.enabled );
        connConfig.setSocketFactory( SSLSocketFactory.getDefault() );
        connConfig.setSASLAuthenticationEnabled( false );
        connConfig.setDebuggerEnabled( false );
		
		conn = new XMPPConnection(connConfig);
		
		this.connect();
	}
	
	public boolean isConnected()
    {
        return conn.isConnected();
    }
	
	public void connect() throws CommunicationException
	{
	    try
        {
            conn.connect();
        }
        catch ( XMPPException e )
        {
            System.out.println( e.getMessage() );
            e.printStackTrace();
            throw new CommunicationException( "Init failed: " + e.getMessage() );
        }
	}
    
    public void login(String user, String pwd) throws CommunicationException
    {
        if ( !conn.isConnected() )
        {
            this.connect();
        }
        try
        {
            conn.login(user, pwd);
            
            Presence presence = new Presence(Presence.Type.available);
            conn.sendPacket(presence);
            
            ChatManager cm = conn.getChatManager();
            chat = cm.createChat(serverName, new MessageListener() {
                
                public void processMessage(Chat c, Message msg) 
                {
                    for( IMessageReceiver mr: msgReceivers )
                    {
                        mr.process( msg );
                    }
                }
            });
        }
        catch( XMPPException e ) 
        {
            System.out.println( e.getMessage() );
            e.printStackTrace();
            throw new CommunicationException( "Connection failed: " + e.getMessage() );
        }
    }
    
    public void sendRawMessage(String msg) throws CommunicationException
    {
        try
        {
            chat.sendMessage(msg);
        }
        catch ( XMPPException e )
        {
            System.out.println( e.getMessage() );
            e.printStackTrace();
            throw new CommunicationException( "Send failed: " + e.getMessage() );
        }
    }
    
    public void addMessageReceiver( IMessageReceiver receiver )
    {
        this.msgReceivers.add( receiver );
    }
    
    public void disconnect()
    {
        conn.disconnect();
        chat = null;
    }

    public synchronized String getReply( String msg ) 
            throws CommunicationException, InterruptedException
    {
        lock.lock();
        lastRequest = msg;
        sendRawMessage( lastRequest );
        replyReady.await();
        lastRequest = null;
        lock.unlock();
        
        return lastReply;
    }
    
    public void process( Message msg )
    {
        lock.lock();
        if ( lastRequest != null )
        {
            lastReply = msg.getBody();
            replyReady.signal();
        }
        lock.unlock();
    }
}
